/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.util.Enumeration;
import javax.swing.text.BadLocationException;
/**
* Document marks enable to store position and line information
* to simplify orientation in the document. They are stored
* in special structure so that all the main operations
* (insert mark, remove mark, insert into document, remove from document,
* getting mark's position, getting mark's line) are relatively cheap.
*
* @author Miloslav Metelka
* @version 1.00
*/
class DocMarks {
/** How many leaves each node has in the tree */
static final int LEAVES_PER_NODE = 16;
/** Absolute count added in each of plane reallocation */
static final int ABSOLUTE_REALLOC_CNT = 128;
/** Relative realloc th - 1/4 of current array size will allocated */
static final int RELATIVE_REALLOC_TH = 4;
/** Maximum of unused marks. If there's more, they are garbage collected */
static final int MAX_UNUSED_MARKS = 100;
/** Some internal messages */
static final String POS_LESS_ZERO = "Position must be >= 0"; // NOI18N
static final String LINE_LESS_ZERO = "Line must be >= 0"; // NOI18N
/** Mark at top of tree */
TreeMark topMark;
/** First mark of leaf plane. It's always the first and it's not removed */
Mark startMark;
/** Top plane */
Plane topPlane;
/** Leaf plane */
Plane leafPlane;
/** Unused plane. There can be only one plane unused. */
Plane unusedPlane;
/** Unused tree marks. They are concatenated in the list by <CODE>parent</CODE>.
* They are reused by the next insert operation.
*/
TreeMark unusedTreeMarks;
/** Unused tree marks count. There is some max. count after which marks are
* simply garbage collected.
*/
int unusedTreeMarksCnt;
/** Count of mark additions to the tree */
int statMarksAdded;
/** Count of full calling getOffset() resp. getOffsetRec() function */
int statPosCalled;
/** Count of full calling getLine() resp. getLineRec() function */
int statLineCalled;
/** Construct new marks */
DocMarks() {
startMark = new Mark() {
public int getOffset() {
return 0;
}
public int getLine() {
return 0;
}
protected void removeUpdateAction(int pos, int len) {
// can't be removed, so no action
}
public void remove() {
// can't be removed
}
};
startMark.insertAfter = true;
startMark.marks = this;
startMark.valid = true;
leafPlane = new Plane(this); // create leaf plane
topPlane = new Plane(this, leafPlane);
leafPlane.marks[0] = startMark; // always the first
leafPlane.markCnt = 1;
topPlane.marks[0] = new TreeMark();
topPlane.marks[0].insertAfter = true;
topPlane.markCnt = 1;
leafPlane.marks[0].parent = topPlane.marks[0];
}
/** Get total mark count */
public synchronized int getMarkCnt() {
return leafPlane.markCnt;
}
/** Insert mark that which was previously created */
public synchronized void insertMark(Mark mark, int pos, int line)
throws BadLocationException, InvalidMarkException {
insertMarkImpl(mark, pos, line);
}
/** Insert update of marks. This function must be called after insert to
* update positions and line numbers of marks. The caller must determine
* the line breaks in inserted text and pass it to this function.
* @param pos position of insertion
* @param len length of inserted data
* @param linesInserted number of line breaks in the inserted part
*/
public synchronized void insertUpdate(int pos, int len, int linesInserted) {
if (len <= 0) {
return;
}
int ind = getIndFromPos(pos, 0, true);
if (ind < leafPlane.markCnt) {
TreeMark m = leafPlane.marks[ind];
while (m != null) {
m.update(len, linesInserted);
m = m.parent;
}
}
}
/** Remove update of marks. This function must be called after remove to
* update positions and line numbers of marks. The caller must determine
* the line breaks in removed text and pass it to this function. The function
* operates in three steps:
* 1. get the array of all marks in removal area and call
* <CODE>removeUpdateAction()</CODE> in all of them.
* 2. find all marks with <CODE>insertAfter</CODE> flag in removal area
* and remove them and immediatelly insert to the begining of removal
* area.
* 3. track the removal area and shrink the distances between marks to zero.
* @param pos position of removal
* @param line line of removal
* @param len length of removed data
* @param linesRemoved number of line breaks in the removed part
*/
public synchronized void removeUpdate(int pos, int line, int len, int linesRemoved) {
if (len <= 0) {
return;
}
TreeMark m = null;
int ind1 = getIndFromPos(pos, line, false); // begining of cleared zone
int posRest1 = leafPlane.tmpInt;
int ind2 = getIndFromPos(pos + len, line, true); // end of zone
if (ind1 == leafPlane.markCnt) { // remove after last mark
return;
}
if (ind1 == ind2) { // No marks removed in this case
m = leafPlane.marks[ind1];
while (m != null) {
m.update(-len, -linesRemoved);
m = m.parent;
}
} else { // One or more marks removed
// Copy marks into new array
Mark marks[] = new Mark[ind2 - ind1];
System.arraycopy(leafPlane.marks, ind1, marks, 0, marks.length);
// Notify all marks in the area about remove
for (int i = 0; i < marks.length; i++) {
if (((Mark)marks[i]).isValid()) {
marks[i].removeUpdateAction(pos, len);
}
}
// At first all marks with insertAfter must be put to the begining
ind1 = getIndFromPos(pos, line, false); // refresh ind1
posRest1 = leafPlane.tmpInt;
int restLen = posRest1 + len;
while (ind1 < leafPlane.markCnt) {
m = leafPlane.marks[ind1];
restLen -= m.relPos;
if (restLen < 0) { // to cover insertAfter marks at pos + len
break;
}
if (m.insertAfter) { // Remove insertAfter marks
restLen += m.relPos; // will be removed so space for next one
try {
removeMarkImpl((Mark)m);
insertMarkImpl((Mark)m, pos, line);
} catch(BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
} catch(InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
// must correct restLen
restLen = pos + len - ((Mark)leafPlane.marks[ind1]).getOffsetRec(leafPlane);
}
ind1++;
}
// now modify the rest of marks (surely have insertAfter == false)
ind1 = getIndFromPos(pos, line, false);
posRest1 = leafPlane.tmpInt;
// int lineRest1 = line - ((Mark)leafPlane.marks[ind1 - 1]).getLineRec(leafPlane);
int lineRest1 = topPlane.tmpInt; // get line rest
int deltaPos; // delta by which mark pos should be decreased
int deltaLine;
while (ind1 < leafPlane.markCnt && len > 0) {
m = leafPlane.marks[ind1];
deltaPos = Math.min(m.relPos - posRest1, len);
deltaLine = Math.min(m.relLine - lineRest1, linesRemoved);
while (m != null) {
m.update(-deltaPos, -deltaLine);
m = m.parent;
}
len -= deltaPos;
linesRemoved -= deltaLine;
posRest1 = 0;
lineRest1 = 0;
ind1++;
}
}
}
/** Get the mark that is either start of document mark
* or the first mark that is most on left of marks with same
* position as parameter or, if no such mark exist, that is
* less than the parameter.
* @param pos requested position
*/
public synchronized Mark getLeftMark(int pos) {
if (pos <= 0) {
return startMark;
}
int ind = getIndFromPos(pos - 1, 0, false);
if (ind >= leafPlane.markCnt
|| leafPlane.marks[ind].relPos - leafPlane.tmpInt > 1
) {
// the distance of the found mark is more than 1 position
// further from requested position (pos - 1) so we must
// go one index back
ind--;
}
return (Mark)leafPlane.marks[ind];
}
public synchronized Mark getLeftMark(int pos, Class markClass) {
if (pos <= 0) {
return null; // for this special case return null
}
int ind = getIndFromPos(pos - 1, 0, false);
if (ind >= leafPlane.markCnt
|| leafPlane.marks[ind].relPos - leafPlane.tmpInt > 1
) {
// the distance of the found mark is more than 1 position
// further from requested position (pos - 1) so we must
// go one index back
ind--;
}
for (int i = ind; i >= 0; i--) {
if (markClass.isInstance(leafPlane.marks[i])) {
return (Mark)leafPlane.marks[i];
}
}
return null;
}
/** Get mark that is right at given pos or null */
public synchronized Mark getOffsetMark(int pos, Class markClass) {
int ind = getIndFromPos(pos, 0, false);
if (leafPlane.tmpInt > 0) { // no mark right at given pos
return null;
}
ind--; // one position to the left to the correct mark
while (ind >= 0) {
if (markClass == null || markClass.isInstance(leafPlane.marks[ind])) {
return (Mark)leafPlane.marks[ind];
}
if (leafPlane.marks[ind].relPos != 0) { // not on the same position as prev
break;
}
ind--; // still on the same position
}
return null;
}
/** Render marks by some <CODE>Renderer</CODE>. It is the most
* efficient way to handle especially multiple adjacent marks.
* Rendering function is called in synchronized manner, so no one will
* modify mark array while executing this function.
*/
public synchronized void render(Renderer r) {
r.marks = this;
r.render();
r.marks = null;
}
/** Gets the nearest lower position for specified line. This
* method can be used when the only line information is available
* and the position is needed (i.e. setting breakpoints, going to line
* with error etc).
* @param line line offset for which we want mark
* @return mark with lower or equal line. Caution! When the caller gets
* the mark and it usually tries to get position of returned mark. However
* the mark can be removed meantime and call <CODE>getOffset()</CODE> will
* throw <CODE>InvalidMarkException</CODE>. In that case caller should
* call <CODE>getMarkFromLine()</CODE> again to get another mark and retry.
*/
synchronized Mark getMarkFromLine(int line) {
if (line < 0)
return (Mark)leafPlane.marks[0]; // return start mark
Plane plane = topPlane; // start from top plane
if (line >= plane.marks[0].relLine) {
return (Mark)(leafPlane.marks[leafPlane.markCnt - 1]);
}
int ind = 0; // index of mark in specific plane
while ((plane = plane.child) != null) {
ind = ind * LEAVES_PER_NODE;
while (line >= plane.marks[ind].relLine) {
line -= plane.marks[ind].relLine;
ind++;
}
}
return (Mark)(leafPlane.marks[ind - 1]); // return mark with lower line
}
/** Internal method for mark insertion */
final void insertMarkImpl(Mark mark, int pos, int line)
throws BadLocationException, InvalidMarkException {
if(pos < 0)
throw new BadLocationException(POS_LESS_ZERO, pos);
if (line < 0)
throw new BadLocationException(LINE_LESS_ZERO, line);
if (mark.marks != null) // marks variable set
throw new InvalidMarkException();
if (mark.valid) // mark is already inserted in tree
throw new InvalidMarkException();
mark.marks = this; // stamp the mark is used by this class
Plane plane = leafPlane; // start from leafPlane
int ind = getIndFromPos(pos, line, mark.insertAfter);
int posRest = plane.tmpInt; // index of previous
// int lineRest = line - ((Mark)plane.marks[ind - 1]).getLineRec(leafPlane);
int lineRest = topPlane.tmpInt; // get line rest
Plane parentPlane; // parent plane of active plane
boolean lastLeafMark = (ind == plane.markCnt);
boolean lastLeafInsertAfter; // insertAfter of last mark
// now we have the insertion point in the leafPlane and we can insert
plane.ensureCapacity(plane.markCnt + 1);
if (!lastLeafMark) {
System.arraycopy(plane.marks, ind, plane.marks, ind + 1, plane.markCnt - ind);
}
plane.marks[ind] = mark;
plane.markCnt++;
mark.init(posRest, lineRest);
if (lastLeafMark) {
lastLeafInsertAfter = mark.insertAfter;
} else {
plane.marks[ind + 1].update(-posRest, -lineRest);
lastLeafInsertAfter = plane.marks[plane.markCnt - 1].insertAfter;
}
boolean markInserted = true; // mark inserted somewhere to current plane
boolean parentMarkInserted; // mark inserted to parent plane
int chunkSize = LEAVES_PER_NODE; // size of chunk
// cycle through all planes except topPlane and update counts
while (plane != topPlane) {
parentPlane = plane.parent;
// test need for allocation of new mark in parent plane
parentMarkInserted = false; // suppose not adding mark to parent
if (markInserted) {
if (plane.markCnt % LEAVES_PER_NODE == 1) {
TreeMark addedMark = getFreshTreeMark();
if (parentPlane == topPlane) { // need to add new top plane
if (unusedPlane != null) {
unusedPlane.child = topPlane;
topPlane.parent = unusedPlane;
topPlane = unusedPlane;
topPlane.markCnt = 0;
unusedPlane = null;
} else {
topPlane = new Plane(this, topPlane);
}
TreeMark topM = getFreshTreeMark();
topPlane.marks[0] = topM;
topPlane.markCnt++;
topM.init(parentPlane.marks[0]);
parentPlane.marks[0].parent = topM;
}
parentPlane.ensureCapacity(parentPlane.markCnt + 1);
parentPlane.marks[parentPlane.markCnt] = addedMark;
parentPlane.markCnt++;
parentMarkInserted = true;
}
if (plane == leafPlane) { // need to correct inserted mark
mark.parent = parentPlane.marks[ind / LEAVES_PER_NODE];
} else {
int lastInd = plane.markCnt - 1;
plane.marks[lastInd].parent = parentPlane.marks[lastInd / LEAVES_PER_NODE];
}
}
if (lastLeafMark) {
plane.marks[plane.markCnt - 1].parent.update(mark.relPos, mark.relLine);
}
int i = (ind / chunkSize) * chunkSize; // tracking index
int chunkBound; // upper bound of current chunk
boolean firstUpdate = true; // signals that the first chunk is being updated
boolean fullChunk; // is the current chunk full or not
while (i < leafPlane.markCnt) {
chunkBound = i + chunkSize; // compute new chunk bound
fullChunk = (leafPlane.markCnt > chunkBound); // full chunk (originally)
parentPlane.marks[i / chunkSize].update( // update parent mark
(firstUpdate ? 0 : leafPlane.marks[i].relPos)
- (fullChunk ? leafPlane.marks[chunkBound].relPos : 0), // update relPos
+ (firstUpdate ? 0 : leafPlane.marks[i].relLine)
- (fullChunk ? leafPlane.marks[chunkBound].relLine : 0), // update relLine
(fullChunk ? leafPlane.marks[chunkBound - 1].insertAfter
: lastLeafInsertAfter) // update insertAfter
);
// update parent of moved mark
if (plane == leafPlane) {
plane.marks[i / (chunkSize / LEAVES_PER_NODE)].parent =
parentPlane.marks[i / chunkSize];
}
i = chunkBound; // go to the next chunk
firstUpdate = false;
}
plane = parentPlane; // go to the parent plane
markInserted = parentMarkInserted;
chunkSize *= LEAVES_PER_NODE;
}
mark.valid = true; // mark is now valid
statMarksAdded++;
}
/** Remove mark from tree. Mark is no longer valid and
* its <CODE>valid</CODE> flag is set to false and its <CODE>marks</CODE>
* variable is set to null.
*/
void removeMarkImpl(Mark mark)
throws InvalidMarkException {
if (!mark.valid)
throw new InvalidMarkException();
if (mark.marks == null)
throw new InvalidMarkException();
Plane plane = leafPlane;
Plane parentPlane;
int ind = mark.getIndRec(leafPlane); // index of mark in leafPlane
boolean lastLeafMark = (ind == --plane.markCnt); // update markCnt now
boolean lastLeafInsertAfter; // insertAfter of last mark
lastLeafInsertAfter = plane.marks[plane.markCnt
- (lastLeafMark ? 1 : 0)].insertAfter;
boolean markRemoved = true;
boolean parentMarkRemoved;
int chunkSize = LEAVES_PER_NODE;
while (plane != topPlane) {
parentPlane = plane.parent;
int i = (ind / chunkSize) * chunkSize;
int chunkBound;
boolean firstUpdate = true;
boolean fullChunk;
while (i <= leafPlane.markCnt) {
chunkBound = i + chunkSize;
fullChunk = (leafPlane.markCnt >= chunkBound);
parentPlane.marks[i / chunkSize].update(
(fullChunk ? leafPlane.marks[chunkBound].relPos : 0)
- (firstUpdate ? 0 : leafPlane.marks[i].relPos),
(fullChunk ? leafPlane.marks[chunkBound].relLine : 0)
- (firstUpdate ? 0 : leafPlane.marks[i].relLine),
(fullChunk ? leafPlane.marks[chunkBound].insertAfter
: lastLeafInsertAfter)
);
// update parent of moved mark
if (plane == leafPlane && fullChunk) {
plane.marks[chunkBound / (chunkSize / LEAVES_PER_NODE)].parent =
parentPlane.marks[i / chunkSize];
}
i = chunkBound; // go to the next chunk
firstUpdate = false;
}
if (lastLeafMark) {
parentPlane.marks[ind / chunkSize].update(-mark.relPos, -mark.relLine);
}
parentMarkRemoved = false; // suppose not adding mark to parent
if (markRemoved && plane.markCnt % LEAVES_PER_NODE == 0) {
if (plane.markCnt == LEAVES_PER_NODE &&
parentPlane.parent == topPlane
) { // need to remove current top plane
putFreshTreeMark(topPlane.marks[0]);
topPlane.markCnt = 0;
unusedPlane = topPlane;
topPlane = topPlane.child;
topPlane.parent = null;
topPlane.marks[0].parent = null; // took me 4 hours to discover
}
putFreshTreeMark(parentPlane.marks[--parentPlane.markCnt]);
parentMarkRemoved = true;
}
plane = parentPlane; // go to the parent plane
markRemoved = parentMarkRemoved;
chunkSize *= LEAVES_PER_NODE;
}
if (!lastLeafMark) {
leafPlane.marks[ind + 1].update(mark.relPos, mark.relLine);
System.arraycopy(leafPlane.marks, ind + 1, leafPlane.marks, ind,
leafPlane.markCnt - ind); // physically remove the mark from leafPlane
}
mark.valid = false; // mark is now invalid
mark.marks = null; // marks reference removed
}
/** Get next greater mark's index from position and return
* advance of given position and line against the position and line of
* the [returned mark's index - 1] mark.
* If there's a mark with the same position as pos, then
* the index of the next mark after all the marks with position pos
* will be returned and the leafPlane.tmpInt will be 0.
* If there's a mark with the lower position only, then the next mark
* with the greater position will be returned and the leafPlane.tmpInt
* will be set to the difference from the requested position minus
* the position of the lower mark.
* @param pos position to find mark for
* @param line line that should be also found
* @param insertAfter if it's set to true then the index of the first
* mark with greater position or same position but having insertAfter set
* to true, is returned.
* @return index of mark found and rest of position from the mark search
* stored in <CODE>leafPlane.tmpInt</CODE> and rest of line search stored
* in <CODE>topPlane.tmpInt</CODE>
*/
final int getIndFromPos(int pos, int line, boolean insertAfter) {
Plane plane = topPlane; // start from top plane
// relPos in first mark in top plane means the whole range
if (pos > plane.marks[0].relPos || (pos == plane.marks[0].relPos
&& (!insertAfter || (insertAfter && plane.marks[0].insertAfter)))
) {
leafPlane.tmpInt = pos - plane.marks[0].relPos;
topPlane.tmpInt = line - plane.marks[0].relLine;
return leafPlane.markCnt;
}
int ind = 0; // index of mark in specific plane
while ((plane = plane.child) != null) {
ind = ind * LEAVES_PER_NODE;
while (pos > plane.marks[ind].relPos ||
(pos == plane.marks[ind].relPos && (!insertAfter
|| (insertAfter && plane.marks[ind].insertAfter)))
) {
pos -= plane.marks[ind].relPos;
line -= plane.marks[ind].relLine;
ind++;
}
}
leafPlane.tmpInt = pos; // store rest of position
topPlane.tmpInt = line; // store rest of line
return ind; // return index
}
/** Get a free tree mark. It's either unused tree mark
* stored in unusedTreeMarks or a new tree mark.
*/
TreeMark getFreshTreeMark() {
TreeMark m;
if (unusedTreeMarks != null) {
m = unusedTreeMarks;
unusedTreeMarksCnt--;
unusedTreeMarks = m.parent;
m.parent = null;
} else {
m = new TreeMark();
}
return m;
}
/** Put a free tree mark. Either put it to list
* of unused marks or let it be garbage collected.
*/
void putFreshTreeMark(TreeMark m) {
if (unusedTreeMarksCnt <= MAX_UNUSED_MARKS) {
m.init(0, 0);
m.insertAfter = false;
m.parent = unusedTreeMarks;
unusedTreeMarks = m;
unusedTreeMarksCnt++;
} // else let it be garbage collected
}
/** One plane in the hierarchy */
static class Plane {
/** Reference for faster getOffsetRec() accesses */
DocMarks docMarks;
/** Array of tree marks */
TreeMark marks[];
/** Size of the array */
int markCnt;
/** Parent plane (closer to tree top) */
Plane parent;
/** Child plane (closer to tree bottom) */
Plane child;
/** Temporary index for use in some functions */
int tmpInt;
/** Construct new empty plane */
Plane(DocMarks docMarks, Plane child) {
this.docMarks = docMarks;
this.child = child;
if (child != null) {
child.parent = this;
}
marks = new TreeMark[ABSOLUTE_REALLOC_CNT];
}
/** Special constructor for creating leaf plane.
* For leaf plane the mark array is real Mark array
* so that <CODE>getMarks()</CODE> in renderer can make conversion.
*/
Plane(DocMarks docMarks) {
this.docMarks = docMarks;
marks = new Mark[ABSOLUTE_REALLOC_CNT];
}
/** Test need for reallocation and if it's needed
* reallocate the array.
*/
void ensureCapacity(int reqCnt) {
if (reqCnt > marks.length) { // need to realloc
TreeMark newMarks[];
int newCnt = marks.length + ABSOLUTE_REALLOC_CNT
+ marks.length / RELATIVE_REALLOC_TH;
newMarks = ((this == docMarks.leafPlane) ? new Mark[newCnt] : new TreeMark[newCnt]);
System.arraycopy(marks, 0, newMarks, 0, markCnt);
marks = newMarks;
}
}
public String toString() {
Plane plane = docMarks.leafPlane;
int ind = 0;
while (plane != null) {
if (plane == this) {
if (plane == docMarks.leafPlane) {
return "LP"; // NOI18N
} else if (plane == docMarks.topPlane) {
return "TP"; // NOI18N
} else {
return "P[" + ind + "]"; // NOI18N
}
}
plane = plane.parent;
ind++;
}
return "Corruption found - unknown plane"; // NOI18N
}
}
/** Basic mark for making tree nodes. It's the class only for
* internal purposes.
*/
static class TreeMark {
/** Relative position */
int relPos;
/** Relative Line number */
int relLine;
/** Parent mark in the tree. It's package private to ease
* chaining of unused marks.
*/
TreeMark parent;
/** Flag describing if the mark should hold its
* position when inserting right on it. If this flag
* is true, the mark's position will stay the same.
* If it's false the insert will move the mark on.
* Bookmarks having this flag set to true must
* occur before those having it set to false in the tree.
*/
boolean insertAfter;
/** Initialize various fields in tree mark */
final void init(int relPos, int relLine) {
this.relPos = relPos;
this.relLine = relLine;
}
/** Initialize <CODE>relPos</CODE>, <CODE>relLine</CODE> and
* <CODE>insertAfter</CODE> from the given mark.
*/
final void init(TreeMark m) {
relPos = m.relPos;
relLine = m.relLine;
insertAfter = m.insertAfter;
}
/** Update the position and line number of this mark.
* This function is used mainly in mark insertion and deletion
* to change both position and line number at once.
* @param posDelta delta increase of <CODE>relPos</CODE>
* @param lineDelta delta increase of <CODE>relLine</CODE>
*/
final void update(int posDelta, int lineDelta) {
relPos += posDelta;
relLine += lineDelta;
}
/** Update the position and line number of this mark.
* This function is used mainly in mark insertion and deletion
* to change both position and line number at once.
* @param posDelta delta increase of <CODE>relPos</CODE>
* @param lineDelta delta increase of <CODE>relLine</CODE>
* @param insertAfter setting for <CODE>insertAfter</CODE> in the mark
*/
final void update(int posDelta, int lineDelta, boolean insertAfter) {
relPos += posDelta;
relLine += lineDelta;
this.insertAfter = insertAfter;
}
/** Recursively get the position of this tree mark
* to find the absolute position of the mark.
*/
protected final int getOffsetRec(Plane plane) {
if (plane == plane.docMarks.topPlane) {
plane.tmpInt = 0;
return 0;
} else { // not top plane
int pos = parent.getOffsetRec(plane.parent);
int ind = plane.parent.tmpInt * LEAVES_PER_NODE;
while (plane.marks[ind] != this) {
pos += plane.marks[ind].relPos;
ind++;
}
plane.tmpInt = ind;
if (plane == plane.docMarks.leafPlane) {
pos += this.relPos;
}
return pos;
}
}
/** Recursively get the line number of this tree mark
* to find the absolute line number of the mark.
*/
protected final int getLineRec(Plane plane) {
if (plane == plane.docMarks.topPlane) {
plane.tmpInt = 0;
return 0;
} else { // not top plane
int line = parent.getLineRec(plane.parent);
int ind = plane.parent.tmpInt * LEAVES_PER_NODE;
while (plane.marks[ind] != this) {
line += plane.marks[ind].relLine;
ind++;
}
plane.tmpInt = ind;
if (plane == plane.docMarks.leafPlane) {
line += this.relLine;
}
return line;
}
}
/** Recursively get the pos and line number of this tree mark
* to find the absolute pos and line number of the mark.
* The line number will be stored in line[], but caller must
* guarantee line[0] = 0.
*/
protected final int getOffsetAndLineRec(Plane plane, int line[]) {
if (plane == plane.docMarks.topPlane) {
plane.tmpInt = 0;
return 0;
} else { // not top plane
int pos = parent.getOffsetAndLineRec(plane.parent, line);
int ind = plane.parent.tmpInt * LEAVES_PER_NODE;
while (plane.marks[ind] != this) {
pos += plane.marks[ind].relPos;
line[0] += plane.marks[ind].relLine;
ind++;
}
plane.tmpInt = ind;
if (plane == plane.docMarks.leafPlane) {
pos += this.relPos;
line[0] += this.relLine;
}
return pos;
}
}
/** Recursively the index of the mark in leafPlane.
* @param plane must be leafPlane initially
*/
protected final int getIndRec(Plane plane) {
if (plane == plane.docMarks.topPlane) {
return 0;
} else {
int ind = parent.getIndRec(plane.parent) * LEAVES_PER_NODE;
while (plane.marks[ind] != this) {
ind++;
}
return ind;
}
}
}
/** More efficient way of handling marks especially if there is a need
* to work with more than one mark at the moment.
*/
public static abstract class Renderer {
DocMarks marks;
/** Getter for marks */
protected final DocMarks getMarks() {
return marks;
}
/** Get array of all marks for document. This array
* can be larger than actual mark count. Therefore it's needed
* to retrieve actual mark count before using that array.
*/
protected Mark[] getMarkArray() {
return (Mark[])marks.leafPlane.marks;
}
/** Get total count of marks in mark array */
protected int getMarkCnt() {
return marks.leafPlane.markCnt;
}
/** Get index of given mark in array of marks returned by
* <CODE>getMarkArray()</CODE>.
*/
protected int getMarkIndex(Mark mark) {
return mark.getIndRec(marks.leafPlane);
}
/** Get the relative distance of mark to previous mark
* in mark array.
*/
protected int getRelPos(Mark mark) {
return mark.relPos;
}
/** Get relative line distance of mark to previous mark
* in mark array.
*/
protected int getRelLine(Mark mark) {
return mark.relLine;
}
/** Rendering function of this mark renderer. It is used
* to make the task this renderer is intended to.
*/
protected abstract void render();
}
/** Get info about <CODE>DocMarks</CODE>. */
public String toString() {
String ret = "getMarkCnt()=" + getMarkCnt() + ", statMarksAdded=" + statMarksAdded // NOI18N
+ ", statPosCalled=" + statPosCalled + ", statLineCalled=" + statLineCalled; // NOI18N
return ret;
}
/** Dump contents of planes. All the marks and planes are listed to system output. */
public String planesToString(Class markClasses[], char markChars[]) {
StringBuffer sb = new StringBuffer();
Plane plane = leafPlane;
sb.append("PLANES DUMP:\n"); // NOI18N
while (plane != null) {
sb.append(plane + ": child=" + plane.child + ", parent=" + plane.parent); // NOI18N
sb.append('\n');
plane = plane.parent;
}
plane = leafPlane;
sb.append("\ni\\P\tAbsPos\tAbsLine\t\n"); // NOI18N
while(plane != null) {
sb.append(plane + ": " + plane.markCnt + " marks\t"); // NOI18N
plane = plane.parent;
}
sb.append('\n');
int sumPos = 0, sumLine = 0;
for (int i = 0; i < leafPlane.markCnt; i++) {
sumPos += leafPlane.marks[i].relPos;
sumLine += leafPlane.marks[i].relLine;
char markChar = '?';
if (markClasses != null) {
for (int j = 0; j < markClasses.length; j++) {
if (markClasses[j].isInstance(leafPlane.marks[i])) {
markChar = markChars[j];
break;
}
}
}
sb.append(markChar + "["+i+"]\t" + sumPos + "\t" + sumLine + "\t"); // NOI18N
plane = leafPlane;
int div = 1;
while(i % div == 0) { // next plane should be disp'd
int planeInd;
planeInd = i / div; // index in plane
if (planeInd < plane.markCnt) {
boolean insertAfter = plane.marks[planeInd].insertAfter;
sb.append(((insertAfter) ? "P=" : "p=") // NOI18N
+ plane.marks[planeInd].relPos
+ "\t" + ((insertAfter) ? "L=" : "l=") // NOI18N
+ plane.marks[planeInd].relLine + "\t"); // NOI18N
}
div *= LEAVES_PER_NODE;
plane = plane.parent; // go to parent plane
if (plane == null)
break;
}
sb.append('\n');
}
return sb.toString();
}
}
/*
* Log
* 15 Gandalf-post-FCS1.13.1.0 3/8/00 Miloslav Metelka
* 14 Gandalf 1.13 1/13/00 Miloslav Metelka
* 13 Gandalf 1.12 1/10/00 Miloslav Metelka
* 12 Gandalf 1.11 11/14/99 Miloslav Metelka
* 11 Gandalf 1.10 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 10 Gandalf 1.9 10/10/99 Miloslav Metelka
* 9 Gandalf 1.8 9/16/99 Miloslav Metelka
* 8 Gandalf 1.7 9/15/99 Miloslav Metelka
* 7 Gandalf 1.6 5/13/99 Miloslav Metelka
* 6 Gandalf 1.5 5/10/99 Miloslav Metelka fix - line elem. mark
* 5 Gandalf 1.4 4/23/99 Miloslav Metelka Undo added and internal
* improvements
* 4 Gandalf 1.3 4/8/99 Miloslav Metelka
* 3 Gandalf 1.2 3/23/99 Miloslav Metelka
* 2 Gandalf 1.1 3/18/99 Miloslav Metelka
* 1 Gandalf 1.0 2/3/99 Miloslav Metelka
* $
*/